//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/io/clocalendpoint.h>

#include <sys/socket.h>
#include <sys/un.h>
#include <buola/io/curi.h>

namespace buola { namespace io {

static int make_socket(ESocketType &pType)
{
#if BUOLA_PLATFORM_MAC
    int lFD=::socket(PF_LOCAL,SOCK_STREAM,0);
    pType&=~ESocketType::PACKET;
#else
    int lFD=::socket(PF_LOCAL,(pType&ESocketType::PACKET)?SOCK_SEQPACKET:SOCK_STREAM,0);
#endif
    if(lFD<0)
        throw XInternal("can't create socket: "+c_error_string(errno));
    return lFD;
}

union UAddress
{
    sockaddr mBase;
    sockaddr_un mLocal;
};

static void fill_address(UAddress &pAddress,int &pAddressLength,const std::string &pPath,bool pAbstract)
{
    if(pAbstract)
    {
#if BUOLA_PLATFORM_MAC
        std::string lName("/tmp/");
        lName+=pPath;
        return fill_address(pAddress,pAddressLength,lName,false);
#else
        if(pPath.length()>sizeof(pAddress.mLocal.sun_path)-2)
            throw XArgument("path too long initializing abstract local socket end point");

        std::memset(pAddress.mLocal.sun_path,0,sizeof(pAddress.mLocal.sun_path));
        std::memcpy(pAddress.mLocal.sun_path+1,pPath.data(),pPath.length());

        pAddress.mLocal.sun_family=AF_LOCAL;
        pAddressLength=(const char*)pAddress.mLocal.sun_path-(const char*)&pAddress.mLocal+pPath.length()+1;
#endif
    }
    else
    {
        if(pPath.length()>sizeof(pAddress.mLocal.sun_path)-1)
            throw XArgument("path too long initializing local socket end point");

        std::memcpy(pAddress.mLocal.sun_path,pPath.data(),pPath.length());
        pAddress.mLocal.sun_family=AF_LOCAL;
        pAddress.mLocal.sun_path[pPath.length()]=0;
        pAddressLength=(const char*)pAddress.mLocal.sun_path-(const char*)&pAddress.mLocal+pPath.length();
    }
}

std::unique_ptr<CSocketAcceptor> CLocalEndPoint::GetBoundSocket(ESocketType pType) const
{
    int lFD=make_socket(pType);
    UAddress lAddress;
    int lAddressLength;
    fill_address(lAddress,lAddressLength,mPath,mAbstract);
    if(::bind(lFD,&lAddress.mBase,lAddressLength)!=0)
    {
        close(lFD);
        throw XInternal("can't bind socket: "+c_error_string(errno));
    }
    return make_unique<CSocketAcceptor>(nFromFD,lFD,pType);
}

io::PSocket CLocalEndPoint::GetConnectedSocket(ESocketType pType) const
{
    int lFD=make_socket(pType);
    UAddress lAddress;
    int lAddressLength;
    fill_address(lAddress,lAddressLength,mPath,mAbstract);
    if(::connect(lFD,&lAddress.mBase,lAddressLength)!=0)
    {
        close(lFD);
        throw XInternal("can't bind socket: "+c_error_string(errno));
    }
    return new io::CSocket(nFromFD,lFD,pType);
}

/*namespace io*/ } /*namespace buola*/ }
